/******************************************************************************

    Copyright (C) 2006 Alessio Stalla (alessiostalla@gmail.com)

    This file is part of PrettyProlog.

    PrettyProlog is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    PrettyProlog is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrettyProlog; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    See the file COPYING for further details.

 *****************************************************************************/

package prettyprolog.datatypes;

import java.util.ArrayList;
import java.util.List;

/**
 * A Callable is a Term formed by a {@link prettyprolog.datatypes.Symbol Symbol}
 * and a list of arguments (i.e. subterms). If the
 * {@link prettyprolog.datatypes.Symbol Symbol} is null, the Callable represents
 * a tuple of values, such as (a,b,c).
 */
public class Callable implements Term {
	
	private static final long serialVersionUID = 1L;

	/** The {@link prettyprolog.datatypes.Symbol Symbol}. */
	protected Symbol sym;
	/** The arguments. */
	protected List<Term> args;
	/** The number of arguments. */
	protected int argn;

	/** Creates a new Callable with a null symbol and an empty arg list. */
	protected Callable() {
		this(null, new ArrayList<Term>());
	}

	/** Creates a new Callable with symbol s and arg list v. */
	public Callable(Symbol s, List<Term> v) {
		sym = s;
		args = new ArrayList<Term>(v);
		argn = args.size();
	}

	/** @return the symbol associated with this Callable. */
	public Symbol getSymbol() {
		return sym;
	}

	/**
	 * @return this Callable's arg list. WARNING: it is an error to modify the
	 *         returned data structure.
	 */
	public List<Term> getArgs() {
		return args;
	}

	/** @return the number of arguments. */
	public int getArgCount() {
		return argn;
	}

	public String toString() {
		return toString(false);
	}
	
	/** @return the String representation of this Callable. */
	public String toString(boolean parensIfInfix) {
		String s = "";
		if (!isInfix() && sym != null) {
			s = sym.toString();
		}
		if (argn > 0) {
			if(!isInfix() || parensIfInfix) {
				s += "(";
			}
			int i;
			for (i = 0; i < argn - 1; i++) {
				s += args.get(i) + (isInfix() ? " " + sym + " " : ", ");
			}
			Term arg = args.get(i); 
			if(isInfix() && arg instanceof Callable) {
				s += ((Callable) arg).toString(true);
			} else {
				s += arg;
			}
			if(!isInfix() || parensIfInfix) {
				s += ")";
			}
		}
		return s;
	}

	protected boolean isInfix() {
		return sym != null && sym.isInfix();
	}

	public Term apply(Substitution subst) {
		List<Term> v = new ArrayList<Term>(args);
		for (int i = 0; i < argn; i++) {
			v.set(i, v.get(i).apply(subst));
		}
		return new Callable(sym, v);
	}

	public boolean isGround() {
		return isGround(true);
	}

	public boolean isGround(boolean strict) {
		for (int i = 0; i < argn; i++) {
			if (!args.get(i).isGround(strict)) {
				return false;
			}
		}
		return true;
	}

	public Callable copy(Substitution vars) {
		List<Term> ls = new ArrayList<Term>();
		for (Term t1 : getArgs()) {
			ls.add(t1.copy(vars));
		}
		return new Callable(getSymbol(), ls);
	}

	public boolean contains(Term t) {
		return contains(t, false);
	}

	public boolean contains(Term t, boolean deep) {
		for (Term t1 : args) {
			if (t1.equals(t) || (deep && t1.contains(t, deep))) {
				return true;
			}
		}
		return false;
	}

	public int hashCode() {
		return 17 * sym.hashCode() + 23 * args.hashCode();
	}

	/**
	 * A Callable c is equal to another object o if
	 * <ul>
	 * <li>o is a Callable,</li>
	 * <li>o.getSymbol().equals(c.getSymbol()) and</li>
	 * <li>o.getArgs().equals(c.getArgs()).</li>
	 * </ul>
	 */
	public boolean equals(Object o) {
		if(this == o) {
			return true;
		}
		if (o instanceof Callable) {
			Callable c = (Callable) o;
			return c.sym.equals(sym) && c.args.equals(args);
		}
		return false;
	}
}